// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// MSVC++ requires this to be set before any other includes to get M_SQRT1_2.
#define _USE_MATH_DEFINES

#include <cmath>
#include <memory>

#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_parameters.h"
#include "media/base/channel_mixer.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

// Number of frames to test with.
enum { kFrames = 16 };

// Test all possible layout conversions can be constructed and mixed.
TEST(ChannelMixerTest, ConstructAllPossibleLayouts)
{
    for (ChannelLayout input_layout = CHANNEL_LAYOUT_MONO;
         input_layout <= CHANNEL_LAYOUT_MAX;
         input_layout = static_cast<ChannelLayout>(input_layout + 1)) {
        for (ChannelLayout output_layout = CHANNEL_LAYOUT_MONO;
             output_layout <= CHANNEL_LAYOUT_MAX;
             output_layout = static_cast<ChannelLayout>(output_layout + 1)) {
            // DISCRETE can't be tested here based on the current approach.
            // CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC is not mixable.
            // Stereo down mix should never be the output layout.
            if (input_layout == CHANNEL_LAYOUT_DISCRETE || input_layout == CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC || output_layout == CHANNEL_LAYOUT_DISCRETE || output_layout == CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC || output_layout == CHANNEL_LAYOUT_STEREO_DOWNMIX) {
                continue;
            }

            SCOPED_TRACE(base::StringPrintf(
                "Input Layout: %d, Output Layout: %d", input_layout, output_layout));
            ChannelMixer mixer(input_layout, output_layout);
            std::unique_ptr<AudioBus> input_bus = AudioBus::Create(ChannelLayoutToChannelCount(input_layout), kFrames);
            std::unique_ptr<AudioBus> output_bus = AudioBus::Create(ChannelLayoutToChannelCount(output_layout), kFrames);
            for (int ch = 0; ch < input_bus->channels(); ++ch)
                std::fill(input_bus->channel(ch), input_bus->channel(ch) + kFrames, 1);

            mixer.Transform(input_bus.get(), output_bus.get());
        }
    }
}

struct ChannelMixerTestData {
    ChannelMixerTestData(ChannelLayout input_layout, ChannelLayout output_layout,
        const float* channel_values, int num_channel_values,
        float scale)
        : input_layout(input_layout)
        , output_layout(output_layout)
        , channel_values(channel_values)
        , num_channel_values(num_channel_values)
        , scale(scale)
    {
        input_channels = ChannelLayoutToChannelCount(input_layout);
        output_channels = ChannelLayoutToChannelCount(output_layout);
    }

    ChannelMixerTestData(ChannelLayout input_layout, int input_channels,
        ChannelLayout output_layout, int output_channels,
        const float* channel_values, int num_channel_values)
        : input_layout(input_layout)
        , input_channels(input_channels)
        , output_layout(output_layout)
        , output_channels(output_channels)
        , channel_values(channel_values)
        , num_channel_values(num_channel_values)
        , scale(1.0f)
    {
    }

    std::string DebugString() const
    {
        return base::StringPrintf(
            "Input Layout: %d, Output Layout %d, Scale: %f", input_layout,
            output_layout, scale);
    }

    ChannelLayout input_layout;
    int input_channels;
    ChannelLayout output_layout;
    int output_channels;
    const float* channel_values;
    int num_channel_values;
    float scale;
};

std::ostream& operator<<(std::ostream& os, const ChannelMixerTestData& data)
{
    return os << data.DebugString();
}

class ChannelMixerTest : public testing::TestWithParam<ChannelMixerTestData> {
};

// Verify channels are mixed and scaled correctly.  The test only works if all
// output channels have the same value.
TEST_P(ChannelMixerTest, Mixing)
{
    ChannelLayout input_layout = GetParam().input_layout;
    int input_channels = GetParam().input_channels;
    std::unique_ptr<AudioBus> input_bus = AudioBus::Create(input_channels, kFrames);
    AudioParameters input_audio(AudioParameters::AUDIO_PCM_LINEAR, input_layout,
        AudioParameters::kAudioCDSampleRate, 16, kFrames);
    if (input_layout == CHANNEL_LAYOUT_DISCRETE)
        input_audio.set_channels_for_discrete(input_channels);

    ChannelLayout output_layout = GetParam().output_layout;
    int output_channels = GetParam().output_channels;
    std::unique_ptr<AudioBus> output_bus = AudioBus::Create(output_channels, kFrames);
    AudioParameters output_audio(AudioParameters::AUDIO_PCM_LINEAR, output_layout,
        AudioParameters::kAudioCDSampleRate, 16,
        kFrames);
    if (output_layout == CHANNEL_LAYOUT_DISCRETE)
        output_audio.set_channels_for_discrete(output_channels);

    const float* channel_values = GetParam().channel_values;
    ASSERT_EQ(input_bus->channels(), GetParam().num_channel_values);

    float expected_value = 0;
    float scale = GetParam().scale;
    for (int ch = 0; ch < input_bus->channels(); ++ch) {
        std::fill(input_bus->channel(ch), input_bus->channel(ch) + kFrames,
            channel_values[ch]);
        expected_value += channel_values[ch] * scale;
    }

    ChannelMixer mixer(input_audio, output_audio);
    mixer.Transform(input_bus.get(), output_bus.get());

    // Validate the output channel
    if (input_layout != CHANNEL_LAYOUT_DISCRETE) {
        for (int ch = 0; ch < output_bus->channels(); ++ch) {
            for (int frame = 0; frame < output_bus->frames(); ++frame) {
                ASSERT_FLOAT_EQ(expected_value, output_bus->channel(ch)[frame]);
            }
        }
    } else {
        // Processing discrete mixing. If there is a matching input channel,
        // then the output channel should be set. If no input channel,
        // output channel should be 0
        for (int ch = 0; ch < output_bus->channels(); ++ch) {
            expected_value = (ch < input_channels) ? channel_values[ch] : 0;
            for (int frame = 0; frame < output_bus->frames(); ++frame) {
                ASSERT_FLOAT_EQ(expected_value, output_bus->channel(ch)[frame]);
            }
        }
    }
}

static float kStereoToMonoValues[] = { 0.5f, 0.75f };
static float kMonoToStereoValues[] = { 0.5f };
// Zero the center channel since it will be mixed at scale 1 vs M_SQRT1_2.
static float kFiveOneToMonoValues[] = { 0.1f, 0.2f, 0.0f, 0.4f, 0.5f, 0.6f };
static float kFiveDiscreteValues[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f };

// Run through basic sanity tests for some common conversions.
INSTANTIATE_TEST_CASE_P(ChannelMixerTest, ChannelMixerTest, testing::Values(ChannelMixerTestData(CHANNEL_LAYOUT_STEREO, CHANNEL_LAYOUT_MONO, kStereoToMonoValues, arraysize(kStereoToMonoValues), 0.5f), ChannelMixerTestData(CHANNEL_LAYOUT_MONO, CHANNEL_LAYOUT_STEREO, kMonoToStereoValues, arraysize(kMonoToStereoValues), 1.0f), ChannelMixerTestData(CHANNEL_LAYOUT_5_1, CHANNEL_LAYOUT_MONO, kFiveOneToMonoValues, arraysize(kFiveOneToMonoValues), static_cast<float>(M_SQRT1_2)), ChannelMixerTestData(CHANNEL_LAYOUT_DISCRETE, 2, CHANNEL_LAYOUT_DISCRETE, 2, kStereoToMonoValues, arraysize(kStereoToMonoValues)), ChannelMixerTestData(CHANNEL_LAYOUT_DISCRETE, 2, CHANNEL_LAYOUT_DISCRETE, 5, kStereoToMonoValues, arraysize(kStereoToMonoValues)), ChannelMixerTestData(CHANNEL_LAYOUT_DISCRETE, 5, CHANNEL_LAYOUT_DISCRETE, 2, kFiveDiscreteValues, arraysize(kFiveDiscreteValues))));

} // namespace media
